package se.mushroomwars.network;

import java.io.IOException;
import java.net.BindException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;

import se.mushroomwars.network.protocol.ReadData;
import se.mushroomwars.network.protocol.SendData;

//TODO create send, broadcast, broadcastexpectone
public class Server extends Listener implements Runnable {
	private int port;
	private Selector sel;
	private ServerSocketChannel server;
	private ReadData reader;
	private SendData sender;
	// each connection gets a unique id
	private int playerID;
	// only used to keep track of who disconnect

	// private List<DataHandleListener> listener;

	/*
	 * public static void main(String argv[]) throws IOException { Server test =
	 * new Server(); test.startServer(50045);
	 * 
	 * }
	 */

	public void run() {
		try {
			startServer(port);
		} catch (IOException e) {
			System.out
					.println("couldnt start the server, but the server is still awesome..");
			e.printStackTrace();
			System.exit(0);
		}
	}

	public Server(int port) {
		server = null;
		playerID = 0;
		this.port = port;
		sender = new SendData();
		reader = new ReadData();
		// extended from Listener
		listener = new ArrayList<DataHandleListener>();
	}

	public void initServer(int port) throws IOException {
		this.port = port;
		sel = Selector.open();
		server = ServerSocketChannel.open();
		// non-blocking socket
		server.configureBlocking(false);

		InetAddress addr = InetAddress.getLocalHost();
		InetSocketAddress saddr = new InetSocketAddress(addr, port);
		try {
			server.socket().bind(saddr);
		} catch (BindException e) {
			System.out
					.println("Port already in use, please choose another one");
			System.exit(-1); // (!)
		}
	}

	private void accept(SelectionKey key) throws IOException {
			// for an accept to be pending the channel must be a server socket
			// channel.
			ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key
					.channel();

			// accept the connection and make it non-blocking
			SocketChannel socketChannel = serverSocketChannel.accept();
			// Socket socket = socketChannel.socket();
			socketChannel.configureBlocking(false);

			// register the new SocketChannel with our Selector, indicating
			// we'd like to be notified when there's data waiting to be read
			socketChannel.register(sel, SelectionKey.OP_READ
					| SelectionKey.OP_WRITE);

			notifyPlayerConnected(socketChannel, playerID);
			playerID++;
	}

	public void startServer(int port) throws IOException {
		this.initServer(port);
		SelectionKey acceptKey = server.register(sel, SelectionKey.OP_ACCEPT);

		System.out.println("Waiting for connections. IP: "
				+ InetAddress.getLocalHost() + " port: " + port);

		// block until incomming connection.
		while (acceptKey.selector().select() > 0) {
			Set<SelectionKey> readyKeys = sel.selectedKeys();
			Iterator<SelectionKey> i = readyKeys.iterator();

			// iterate over the sockets
			while (i.hasNext()) {
				SelectionKey key = (SelectionKey) i.next();
				i.remove();

				// accept incomming connection
				if (key.isAcceptable()) {
					accept(key);
					System.out.println("PlayerID: " + playerID + " Connected");
				}

				// data to read
				// the only requirement for a vailde package is that the header
				// is correct
				// if it's malformed, the client will be disconnected
				else if (key.isReadable()) {
					Object dataPackage = null;
					SocketChannel sock = (SocketChannel) key.channel();
					try {
						dataPackage = reader.read(sock);
						// something went wrong with the client, disconnect the
						// client
					} catch (Exception e) {
						disconnectClient(sock);
					}

					// tell the listeners that we have data to handle
					// allow null-datapackage to be sent(!)
					// but will ignore it without disconnect the player
					if (dataPackage != null) {
						//assert players.find(sock) != null;
						notifyListener(sock, dataPackage,
								reader.getPackageID(sock));
					}
				}

				// heck if there is data to be sent
				else if (key.isWritable()) {
					// return false on IOERROR, disconnect
					if (!sender.send((SocketChannel) key.channel()))
						disconnectClient((SocketChannel) key.channel());
				}
			}
		}
	}
	
	public boolean send(SocketChannel socket, Object data, int packageID) {
		return sender.send(socket, data, packageID);
	}

	public void disconnectClient(SocketChannel socket) {
			try {
				socket.close();
				//assert players.find(socket) != null;
				notifyPlayerDisconnected(socket);
			} catch (IOException e) {
		}
	}

}
